/*
 * Copyright (C) 2025 ameise <ameise.wang@gmail.com> - All Rights Reserved
 *
 * This file is part of e3time.
 *
 * e3time is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * e3time is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with e3time. If not, see <https://www.gnu.org/licenses/>.
 */

package e3time

import (
	"gitee.com/ameise84/e3ds/heap/compare"
	"gitee.com/ameise84/e3pool/obj_pool"
	"sync/atomic"
)

var (
	_gInstID    atomic.Uint64
	_gTimerPool obj_pool.Pool[*timer]
	zeroTime    Time
)

func init() {
	_gTimerPool = obj_pool.NewPool[*timer](
		func() *timer {
			return &timer{instID: _gInstID.Add(1)}
		})
}

func takeTimer(lp *holder, cb TimerHandler, ctx any, fireAt Time, dur Duration, repeat int) *timer {
	tr := _gTimerPool.Get()
	tr.srv = lp
	tr.cb = cb
	tr.ctx = ctx
	tr.fireAt = fireAt
	tr.dur = dur
	tr.repeat = repeat
	return tr
}

func freeTimer(tr *timer) {
	tr.srv = nil
	tr.cb = nil
	tr.ctx = nil
	tr.repeat = 0
	_gTimerPool.Put(tr)
}

type timer struct {
	instID      uint64
	isInTrigger atomic.Bool
	srv         *holder
	cb          TimerHandler
	ctx         any
	fireAt      Time
	dur         Duration
	repeat      int //重复次数
}

func (ts *timer) UniqueID() uint64 {
	if ts.ctx != nil {
		if tr, ok := ts.ctx.(WithUniqueID); ok {
			return tr.UniqueID()
		}
	}
	return ts.instID
}

func (ts *timer) Compare(other *timer) int {
	if ts.fireAt.Before(other.fireAt) {
		return compare.Smaller
	}
	return compare.Larger
}

func (ts *timer) Context() any {
	return ts.ctx
}

func (ts *timer) Reset(dur Duration, repeat ...int) (Time, bool) {
	rep := 1
	if repeat != nil {
		rep = repeat[0]
		if !(rep == InfiniteTimes || rep > 1) || dur == 0 {
			return zeroTime, false
		}
	}
	fireAt := Now().Add(dur)
	ts.srv.modify(ts, fireAt, dur, rep)
	return fireAt, true
}

func (ts *timer) Pause() {
	ts.srv.pause(ts)
}

func (ts *timer) Revert() {
	ts.srv.modify(ts, ts.fireAt, ts.dur, ts.repeat)
}

func (ts *timer) fire(evt *event, now Time) bool {
	doTimes := 1
	if ts.repeat == InfiniteTimes || ts.repeat > 1 {
		doTimes = int(now.Sub(ts.fireAt)/ts.dur) + 1
		if ts.repeat > 1 && doTimes > ts.repeat {
			doTimes = ts.repeat
		}
	}

	evt.tr = ts
	evt.doTimes = doTimes
	evt.fireAt = ts.fireAt
	evt.dur = ts.dur

	if ts.repeat != InfiniteTimes {
		ts.repeat -= doTimes
	}
	if ts.repeat != 0 {
		ts.fireAt = ts.fireAt.Add(Duration(doTimes) * ts.dur)
		return true
	}
	return false
}
